﻿using Coding4Fun.MissionControl.API.Data;
using Coding4Fun.MissionControl.API.Models;
using Newtonsoft.Json;
using Phidgets;
using Phidgets.Events;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Coding4Fun.MissionControl.API.Helpers
{
    public class CommandHelper
    {
        // The connected Phidget IR sensor.
        // Not active by default.
        static IR sensor;

        /// <summary>
        /// Interpret a command that was captured from an inbound device.
        /// </summary>
        /// <param name="rawCommand">The string content of the command.</param>
        /// <param name="sourceLocation">The location (IP) of the inbound device.</param>
        public static void InterpretCommand(string rawCommand, string sourceLocation)
        {
            // Make sure that we are not attempting to process a dummy command, therefore
            // causing an exception.
            if (!string.IsNullOrWhiteSpace(rawCommand))
            {
                KeyValuePair<string, string> result = JsonConvert.DeserializeObject<KeyValuePair<string, string>>(rawCommand.Remove(0, rawCommand.IndexOf('{')));

                // Get the initial list of sets on the target server
                if (result.Key == Constants.COMMAND_INIT)
                {
                    SendSets(sourceLocation);
                }
                // Create a new set on the target server
                else if (result.Key.Contains(Constants.COMMAND_CREATE_SET))
                {
                    CreateSet(result, sourceLocation);
                    SendSets(sourceLocation);
                }
                // Get the commands that are associated with a given set.
                else if (result.Key == Constants.COMMAND_GET_COMMANDS)
                {
                    SendCommands(result.Value, sourceLocation);
                }
                // The client requested the server to learn a new command.
                else if (result.Key.Contains(Constants.COMMAND_LEARN_COMMAND))
                {
                    LearnCommand(result, sourceLocation);
                }
                // The client requested one of the commands to be executed on the 
                // target server.
                else if (result.Key.Contains(Constants.COMMAND_EXECUTE))
                {
                    ExecuteCommand(result);
                }
                // The client has requested a set to be deleted from the target server.
                else if (result.Key == Constants.COMMAND_DELETE_SET)
                {
                    DeleteSet(result.Value);
                    SendSets(sourceLocation);
                }
                // The client has requested a set to be deleted from the target server.
                else if (result.Key.Contains(Constants.COMMAND_DELETE_COMMAND))
                {
                    DeleteCommand(result);
                    SendCommands(result.Key.Split(new char[] { ':' })[1], sourceLocation);
                }
            }
        }

        private static void DeleteCommand(KeyValuePair<string, string> result)
        {
            var sets = StorageHelper.GetSets();
            string setName = result.Key.Split(new char[] {':'})[1];
            var targetSet = StorageHelper.GetSingleSet(sets, setName);
            var command = (from c in targetSet.Commands where c.Name == result.Value select c).FirstOrDefault();

            if (command != null)
            {
                targetSet.Commands.Remove(command);
                StorageHelper.SerializeSets(sets);
            }
        }

        /// <summary>
        /// Send the list of sets to the client that requested those.
        /// </summary>
        /// <param name="sourceLocation">The location of the requesting client.</param>
        private static void SendSets(string sourceLocation)
        {
            Console.WriteLine("Received an initial set query from {0}", sourceLocation);

            ServerResponse response = new ServerResponse();
            response.Marker = "SET_LIST";
            response.Content = JsonConvert.SerializeObject(StorageHelper.GetRawSetNames());
            response.Identifier = NetworkHelper.GetLocalIPAddress().ToString();

            NetworkHelper.SendData(sourceLocation, JsonConvert.SerializeObject(response));
            Console.WriteLine("Sent the set list to {0}", sourceLocation);
        }

        /// <summary>
        /// Create a new set and store it on the local server.
        /// </summary>
        /// <param name="result">The original deserialized command.</param>
        /// <param name="sourceLocation">The location of the requesting client.</param>
        private static void CreateSet(KeyValuePair<string, string> result, string sourceLocation)
        {
            bool isSuccessful = false;
            string[] data = result.Key.Split(new char[] { ':' });

            Console.WriteLine("There is an attempt to create the {0} set from {1}.", result.Value, sourceLocation);

            if (data[1].ToLower() == "list")
                isSuccessful = StorageHelper.AddSet(result.Value);
            else
                isSuccessful = StorageHelper.AddSet(result.Value, false);

            if (isSuccessful)
                Console.WriteLine("The {0} set was successfully created.", result.Value);
            else
                Console.WriteLine("Something happened and the {0} set was not created.", result.Value);
        }

        /// <summary>
        /// Send a list of commands that are associated with the pushed set.
        /// </summary>
        /// <param name="setName">The original deserialized command.</param>
        /// <param name="sourceLocation">The location of the requesting client.</param>
        private static void SendCommands(string setName, string sourceLocation)
        {
            Console.WriteLine("There was a request to get the commands for the {0} set from {1}.", setName, sourceLocation);

            bool isList = StorageHelper.IsSetAList(setName);

            ServerResponse response = new ServerResponse();
            response.Marker = string.Format("SET_COMMANDS:{0}:{1}", setName, isList);
            response.Identifier = NetworkHelper.GetLocalIPAddress().ToString();
            response.Content = JsonConvert.SerializeObject(StorageHelper.GetRawCommandNames(setName));

            NetworkHelper.SendData(sourceLocation, JsonConvert.SerializeObject(response));

            Console.WriteLine("Command list for the {0} set were sent to {1}.", setName, sourceLocation);
        }

        /// <summary>
        /// Learn a new command and store it on the target server.
        /// </summary>
        /// <param name="result">The original deserialized command.</param>
        /// <param name="sourceLocation">The location of the requesting client.</param>
        private static void LearnCommand(KeyValuePair<string, string> result, string sourceLocation)
        {
            Console.WriteLine("[!] Server in COMMAND LEARNING MODE! Point the remote towards the sensor and send a command.");

            string[] data = result.Key.Split(new char[] { ':' });
            var set = StorageHelper.GetSingleSet(StorageHelper.GetSets(), data[1]);

            if (set != null)
            {
                if ((from c in set.Commands where c.Name == result.Value select c).FirstOrDefault() != null)
                {
                    Console.WriteLine("Cannot learn command {0} for the following set: {1}. Command already exists.", data[1], result.Value);

                    ServerResponse response = new ServerResponse();
                    response.Marker = "NOTIFICATION";
                    response.Identifier = NetworkHelper.GetLocalIPAddress().ToString();
                    response.Content = "We could not save the following command - " + result.Value + ". It already exists in the set.";

                    NetworkHelper.SendData(sourceLocation, JsonConvert.SerializeObject(response));
                }
                else
                {
                    IRLearnEventHandler localHandler = null;
                    localHandler = (sender, args) =>
                    {
                        Console.WriteLine("[!] Server learned the command and is no longer in COMMAND LEARNING MODE.");

                        IRLearnedCode code = args.LearnedCode;
                        code.CodeInfo.MinRepeat = 5;

                        Command command = new Command();
                        command.Name = result.Value;
                        command.Code = IRCodeWorker.GetSerializableIRCode(code);

                        StorageHelper.AddCommand(command, set.Name);

                        ServerResponse response = new ServerResponse();
                        response.Marker = "NOTIFICATION";
                        response.Identifier = NetworkHelper.GetLocalIPAddress().ToString();
                        response.Content = "The following command has been stored: " + result.Value;

                        NetworkHelper.SendData(sourceLocation, JsonConvert.SerializeObject(response));
                        sensor.Learn -= localHandler;
                    };

                    if (sensor == null)
                        sensor = new IR();

                    ThreadHelper.DoWork(() =>
                    {
                        sensor.open(-1);
                        sensor.waitForAttachment();
                        sensor.Learn += localHandler;
                    }, 10000);
                }
            }
        }

        /// <summary>
        /// Execute one of the commands currently stored on the local server.
        /// </summary>
        /// <param name="result">The original deserialized command.</param>
        private static void ExecuteCommand(KeyValuePair<string, string> result)
        {
            string[] data = result.Key.Split(new char[] { ':' });
            var set = StorageHelper.GetSingleSet(StorageHelper.GetSets(), data[1]);
            if (set != null)
            {
                var command = StorageHelper.GetSingleCommand(StorageHelper.GetCommands(set.Name), result.Value);

                IRLearnedCode code = IRCodeWorker.GetLearnedCode(command.Code);

                if (code != null)
                {
                    if (sensor == null)
                        sensor = new IR();

                    ThreadHelper.DoWork(() =>
                    {
                        sensor.open(-1);
                        sensor.waitForAttachment();

                        if (sensor.Attached)
                        {
                            sensor.transmit(code.Code, code.CodeInfo);
                        }

                        sensor.close();
                    }, 3000);
                }
                else
                {
                    Console.WriteLine("Code was null, could not send.");
                }
            }
        }

        /// <summary>
        /// Delete a single set and all the associated commands
        /// </summary>
        /// <param name="target">The name of the set.</param>
        private static void DeleteSet(string target)
        {
            var sets = StorageHelper.GetSets();
            var targetSet = StorageHelper.GetSingleSet(sets, target);

            if (targetSet != null)
            {
                StorageHelper.RemoveSet(sets, targetSet);
            }
        }

    }
}

